# coding=utf-8
"""
作者：dnscmd@qq.com
创建时间：2024/12/17 15:42
描述：
"""
import datetime
import os
import re
import time
from decimal import Decimal

import Levenshtein
import pandas as pd
import wx
import wx.xrc
from docx import Document  # python-docx
from markitdown import MarkItDown

from visio_device_stat import VisioDeviceStat

int_pattern = re.compile(r'\d+')


def to_int(s: str):
    if isinstance(s, int):
        return s
    if isinstance(s, float):
        return int(s)
    match = int_pattern.search(s)
    if not match:
        return 0
    return int(match.group())


def process_cell(value):
    if isinstance(value, (int, float)):  # 检查是否为整数或浮点数
        s = str(value)  # 转换为字符串
        if '.' in s:
            l = len(s.split('.')[-1])
            if l > 3:
                return s[:len(s) - (l - 3)]
            return s

    return value


class FrmCompare(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition,
                          size=wx.Size(1920, 1010), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)

        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)

        bs = wx.BoxSizer(wx.VERTICAL)

        gs = wx.GridSizer(1, 2, 0, 0)

        self.txt_in_left = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(940, 400),
                                       wx.TE_MULTILINE)
        gs.Add(self.txt_in_left, 0, wx.ALL, 5)
        self.txt_in_left.SetToolTip("左侧输入")
        self.txt_in_left.SetFont(wx.Font(16, 70, 90, 90, False, wx.EmptyString))

        self.txt_in_right = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(940, 400),
                                        wx.TE_MULTILINE)
        gs.Add(self.txt_in_right, 0, wx.ALL, 5)
        self.txt_in_right.SetToolTip("右侧输入")
        self.txt_in_right.SetFont(wx.Font(16, 70, 90, 90, False, wx.EmptyString))

        bs.Add(gs, 1, wx.EXPAND, 5)

        gs3 = wx.GridSizer(0, 2, 0, 0)

        self.txt_out_left = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(940, 500),
                                        wx.TE_MULTILINE)
        gs3.Add(self.txt_out_left, 0, wx.ALL, 5)
        self.txt_out_left.SetToolTip("左侧输出")
        self.txt_out_left.SetFont(wx.Font(16, 70, 90, 90, False, wx.EmptyString))

        self.txt_out_right = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(940, 500),
                                         wx.TE_MULTILINE)
        gs3.Add(self.txt_out_right, 0, wx.ALL, 5)
        self.txt_out_right.SetToolTip("右侧输出")
        self.txt_out_right.SetFont(wx.Font(16, 70, 90, 90, False, wx.EmptyString))

        bs.Add(gs3, 1, wx.EXPAND, 5)

        gs4 = wx.GridSizer(1, 16, 0, 0)

        self.btn_clear = wx.Button(self, wx.ID_ANY, u"清 空", wx.DefaultPosition, wx.Size(100, 40), 0)
        self.btn_clear.SetToolTip("清空所有文本框")
        gs4.Add(self.btn_clear, 0, wx.ALL, 5)

        self.btn_diff = wx.Button(self, wx.ID_ANY, u"差 异", wx.DefaultPosition, wx.Size(100, 40), 0)
        self.btn_diff.SetToolTip("比较左右两侧文本差异")
        gs4.Add(self.btn_diff, 0, wx.ALL, 5)

        self.btn_sum_first_minus = wx.Button(self, wx.ID_ANY, u"左减右", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_sum_first_minus, 0, wx.ALL, 5)
        self.btn_sum_first_minus.SetToolTip("左侧之和减去右侧之和，数字可由换号符，逗号，空格等连接")

        self.btn_stat_vsdx = wx.Button(self, wx.ID_ANY, u"vsdx设备统计", wx.DefaultPosition, wx.Size(100, 40), 0)
        self.btn_stat_vsdx.SetToolTip("对visio第一页内的关键设备进行统计")
        gs4.Add(self.btn_stat_vsdx, 0, wx.ALL, 5)

        self.btn_docx2md = wx.Button(self, wx.ID_ANY, u"转换为md", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_docx2md, 0, wx.ALL, 5)
        self.btn_docx2md.SetToolTip('将文件下docx转换为md（不转换子目录下文件）')

        self.btn_select_dir = wx.Button(self, wx.ID_ANY, u"选择文件夹", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_select_dir, 0, wx.ALL, 5)

        self.btn_open_file = wx.Button(self, wx.ID_ANY, u"选择文件", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_open_file, 0, wx.ALL, 5)

        self.btn_oepn_file2 = wx.Button(self, wx.ID_ANY, u"选择文件2", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_oepn_file2, 0, wx.ALL, 5)
        self.btn_oepn_file2.SetToolTip('选择对比文件')

        self.btn_diff_excel = wx.Button(self, wx.ID_ANY, u"xls值比较", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_diff_excel, 0, wx.ALL, 5)
        self.btn_diff_excel.SetToolTip('假设：两个excel对应sheet内行相同，否则以最少的那个计算')

        self.btn_tuoming = wx.Button(self, wx.ID_ANY, u"脱敏", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_tuoming, 0, wx.ALL, 5)
        self.btn_tuoming.SetToolTip('文本脱敏（如果选择了文件夹，结果输出为txt文件）')

        self.btn_un_tuoming = wx.Button(self, wx.ID_ANY, u"反脱敏", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_un_tuoming, 0, wx.ALL, 5)
        self.btn_un_tuoming.SetToolTip('将脱敏后的文本进行还原（不输出文件）')

        self.btn_extract_docx_titles = wx.Button(self, wx.ID_ANY, u"doc5", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_extract_docx_titles, 0, wx.ALL, 5)
        self.btn_extract_docx_titles.SetToolTip('从docx中提取5级标题')

        self.btn_extract_docx_titles4 = wx.Button(self, wx.ID_ANY, u"doc4", wx.DefaultPosition, wx.Size(100, 40), 0)
        gs4.Add(self.btn_extract_docx_titles4, 0, wx.ALL, 5)
        self.btn_extract_docx_titles4.SetToolTip('从docx中提取4级标题')

        self.lb_path = wx.StaticText(self, wx.ID_ANY, u"...", wx.DefaultPosition, wx.DefaultSize, 0)
        self.lb_path.Wrap(-1)
        gs4.Add(self.lb_path, 0, wx.ALL, 5)

        self.lb_path2 = wx.StaticText(self, wx.ID_ANY, u"...", wx.DefaultPosition, wx.DefaultSize, 0)
        self.lb_path2.Wrap(-1)
        gs4.Add(self.lb_path2, 0, wx.ALL, 5)

        bs.Add(gs4, 1, wx.EXPAND, 5)

        self.SetSizer(bs)
        self.Layout()

        self.Centre(wx.BOTH)

        # 绑定按钮点击事件
        self.btn_clear.Bind(wx.EVT_BUTTON, self.clear_txts)
        self.btn_diff.Bind(wx.EVT_BUTTON, self.diff_txts)
        self.btn_sum_first_minus.Bind(wx.EVT_BUTTON, self.left_minus_right)
        self.btn_open_file.Bind(wx.EVT_BUTTON, self.select_file)
        self.btn_stat_vsdx.Bind(wx.EVT_BUTTON, self.stat_vsdx)
        self.btn_docx2md.Bind(wx.EVT_BUTTON, self.docx2md)
        self.btn_select_dir.Bind(wx.EVT_BUTTON, self.select_dir)
        self.btn_oepn_file2.Bind(wx.EVT_BUTTON, self.select_file2)
        self.btn_diff_excel.Bind(wx.EVT_BUTTON, self.diff_excel)
        self.btn_tuoming.Bind(wx.EVT_BUTTON, self.tuoming)
        self.btn_un_tuoming.Bind(wx.EVT_BUTTON, self.un_tuoming)
        self.btn_extract_docx_titles.Bind(wx.EVT_BUTTON, self.extract_docx_titles)
        self.btn_extract_docx_titles4.Bind(wx.EVT_BUTTON, self.extract_docx_titles4)

        self._selected_file_path = ''  # 选中的文件路径
        self._selected_file_path2 = ''  # 选中的文件路径
        self._selected_dir_path = ''  # 选中的文件夹路径
        self._replace_words = []

    def __del__(self):
        pass

    def clear_txts(self, event):
        self.txt_in_left.Clear()
        self.txt_in_right.Clear()
        self.txt_out_left.Clear()
        self.txt_out_right.Clear()

    def left_minus_right(self, event):
        """
        左侧的数据进行求和后减去右侧的数据之和
        """
        ls = [Decimal(v) for v in re.split("\n|,|，|\t|；|;| ", self.txt_in_left.GetValue()) if v]
        rs = [Decimal(v) for v in re.split("\n|,|，|\t|；|;| ", self.txt_in_right.GetValue()) if v]

        result = 0
        for v in ls:
            result += v
        for v in rs:
            result -= v
        self.txt_out_left.SetValue("【 左 - 右 】 = " + str(result))

    def select_file(self, event):
        """
        左侧的数据进行求和后减去右侧的数据之和
        """
        self._selected_file_path = ''

        dialog = wx.FileDialog(self, "选择文件", wildcard="All files (*.*)|*.*",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)

        if dialog.ShowModal() == wx.ID_OK:
            self._selected_file_path = dialog.GetPath()

        self.lb_path.LabelText = self._selected_file_path
        dialog.Destroy()

    def select_file2(self, event):
        """
        左侧的数据进行求和后减去右侧的数据之和
        """
        self._selected_file_path2 = ''

        dialog = wx.FileDialog(self, "选择文件", wildcard="All files (*.*)|*.*",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)

        if dialog.ShowModal() == wx.ID_OK:
            self._selected_file_path2 = dialog.GetPath()

        self.lb_path2.LabelText = self._selected_file_path2
        dialog.Destroy()

    def select_dir(self, event):
        """
        左侧的数据进行求和后减去右侧的数据之和
        """
        self._selected_file_path = ''
        self._selected_file_path2 = ''

        dialog = wx.DirDialog(self, "选择文件夹", style=wx.DD_DEFAULT_STYLE)

        if dialog.ShowModal() == wx.ID_OK:
            self._selected_dir_path = dialog.GetPath()

        self.lb_path.LabelText = self._selected_dir_path
        dialog.Destroy()

    @classmethod
    def get_diffs(cls, t1s, t2s, threshold=0.8):
        """
        获取和t1s中文本相似的文本
        """
        txts = []
        for t1 in t1s:
            best_idx = -1
            best_sim = 0
            if not t1: continue
            for idx, t2 in enumerate(t2s):
                if not t2: continue
                distance = Levenshtein.distance(t1, t2)
                similarity = 1 - distance / max(len(t1), len(t2))
                if similarity < threshold: continue
                if similarity > 0.99:
                    best_sim = 1.0
                    break

                if similarity > best_sim:
                    best_sim = similarity
                    best_idx = idx

            if best_sim > 0.99:
                continue

            txts.append((t1, t2s[best_idx] if best_sim > threshold else ''))
        return txts

    def diff_txts(self, event):
        t1s = [v for v in str(self.txt_in_left.GetValue()).split('\n') if v]
        t2s = [v for v in str(self.txt_in_right.GetValue()).split('\n') if v]
        # t1s.sort()
        # t2s.sort() #排序后快归快，但不利于用户查看

        rights = FrmCompare.get_diffs(t2s, t1s)
        lefts = FrmCompare.get_diffs(t1s, t2s)

        self.txt_out_left.Clear()
        out_txts = []
        if lefts:
            for l, r in lefts:
                if not r:
                    out_txts.append(f'【缺失】{l}\n--------------------------------------------------')
                else:
                    out_txts.append(f'{l}\n    <--【差异】-->\n{r}\n--------------------------------------------------')
            self.txt_out_left.SetValue('\n'.join(out_txts))

        self.txt_out_right.Clear()
        out_txts.clear()
        if rights:
            for l, r in rights:
                if not r:
                    out_txts.append(f'【缺失】{l}\n--------------------------------------------------')
                else:
                    out_txts.append(f'{l}\n    <--【差异】-->\n{r}\n--------------------------------------------------')
            self.txt_out_right.SetValue('\n'.join(out_txts))

    def stat_vsdx(self, event):
        self.txt_out_left.SetValue('')
        self.txt_out_right.SetValue('')

        if not self._selected_file_path or not self._selected_file_path.endswith('.vsdx'):
            return

        stat = VisioDeviceStat(self._selected_file_path)
        dev_ls, no_dev_ls = stat.split_device_num()
        if dev_ls:
            self.txt_out_left.SetValue('\n'.join([f'{v[0]}\t{v[1]}' for v in dev_ls]))

        if no_dev_ls:
            self.txt_out_right.SetValue('\n'.join(no_dev_ls))

    @staticmethod
    def excel_to_markdown(excel_file, output_file) -> bool:
        """
        将Excel文件中的所有sheet转换为Markdown格式，并存放在一个文件中。

        Args:
            excel_file (str): Excel文件路径。
            output_file (str): 输出Markdown文件路径。
        """

        try:
            with pd.ExcelFile(excel_file) as xls:
                with open(output_file, "w", encoding="utf-8") as f:
                    for sheet_name in xls.sheet_names:
                        df = xls.parse(sheet_name)

                        # 添加sheet标题
                        f.write(f"## {sheet_name}\n\n")

                        # 将DataFrame转换为Markdown格式
                        markdown_table = df.to_markdown(index=False)

                        # 写入Markdown表格
                        f.write(markdown_table)
                        f.write("\n\n")  # 添加分隔符
            return True

        except Exception as e:
            print(f"发生错误: {e}")
        return False

    def docx2md(self, event):
        self.txt_out_right.SetValue("")
        results = [f'{str(datetime.datetime.now())}']

        if not self._selected_dir_path:
            self.txt_out_right.SetValue("请选择docx文件夹")
            return

        self.txt_out_right.SetValue("开始转换...")
        md_dir = os.path.join(self._selected_dir_path, 'md')
        if not os.path.isdir(md_dir):
            os.mkdir(md_dir)

        markitdown = MarkItDown()

        # 统计一下数量
        file_ls = []
        for fn in os.listdir(self._selected_dir_path):
            if fn.endswith('.xlsx') or fn.endswith('.xls') or fn.endswith('.docx'):
                file_ls.append(fn)
        if not file_ls:
            self.txt_out_right.SetValue("文件夹下没有文件")
            return

        dialog = wx.ProgressDialog("进度条", "正在执行任务...", maximum=len(file_ls),
                                   style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME)

        for fn in os.listdir(self._selected_dir_path):
            filename = os.path.join(self._selected_dir_path, fn)
            md_file = os.path.join(md_dir, os.path.splitext(os.path.basename(filename))[0] + '.md')

            if fn.endswith('.xlsx') or fn.endswith('.xls'):
                if FrmCompare.excel_to_markdown(filename, md_file):
                    results.append(f'【ok】 {fn}')
                else:
                    results.append(f'fail {fn}')
                dialog.Update(len(results) - 1, f'完成 {fn}')
                continue

            if fn.endswith('.docx'):
                result = markitdown.convert(filename)
                with open(md_file, 'w', encoding='utf-8') as f:
                    f.write(result.text_content)
                dialog.Update(len(results) - 1, f'完成 {fn}')
                results.append(f'【ok】 {fn}')

        dialog.Close()
        dialog.Destroy()
        self.txt_out_right.SetValue('\n'.join(results))

    def read_xlsx(self, file_path):
        sheet_name_ls, data_ls = [], []
        with pd.ExcelFile(file_path) as xls:
            for sheet_name in xls.sheet_names:
                sheet_name_ls.append(sheet_name)
                df = xls.parse(sheet_name)
                df = df.fillna('')

                for col in df.columns:
                    df[col] = df[col].apply(process_cell)  # 将转换函数应用于所有列

                data_ls.append(df.values.tolist())
        return sheet_name_ls, data_ls

    @staticmethod
    def add_red_bak(txt):
        if not txt:
            return ''

        return f'<font color="red"> {txt} </font>'

    def is_diff(self, a, b):
        a1, b1 = [], []
        is_equal = True
        for v1, v2 in zip(a, b):
            if type(v1) != type(v2):
                is_equal = False
                a1.append(FrmCompare.add_red_bak(str(v1)))
                b1.append(FrmCompare.add_red_bak(str(v2)))
                continue

            if v1 != v2:
                is_equal = False
                a1.append(FrmCompare.add_red_bak(str(v1)))
                b1.append(FrmCompare.add_red_bak(str(v2)))
                continue

            a1.append(str(v1))
            b1.append(str(v2))

        if is_equal:
            return ''

        a1 = ' | '.join([v for v in a1])
        b1 = ' | '.join([v for v in b1])
        c1 = ' | '.join(['' for v in a1])
        return f'|{a1}|\n|{b1}|\n{c1}\n'  # a1 + '\n' + b1

    def _load_replace_words(self):
        if not self._replace_words:
            fname = 'tuoming.txt'
            if not os.path.isfile(fname):
                with open(fname, 'w', encoding='utf8') as f:
                    f.write("")
                self.txt_out_right.SetValue("请配置替换词汇")
                return

            with open(fname, 'r', encoding='utf8') as f:
                self._replace_words = f.readlines()

            self._replace_words = [v.strip().split(' ') for v in self._replace_words]

    def replace_words(self, text):
        self._load_replace_words()
        replace_words = {v[0]: v[1] for v in self._replace_words}

        pattern = re.compile("|".join(map(re.escape, replace_words.keys())))  # 预编译
        return pattern.sub(lambda m: replace_words[m.group(0)], text)

    def tuoming(self, event):
        '''
        脱敏（主要将地点这些替换掉
        '''
        raw_txt = self.txt_in_left.GetValue()
        if raw_txt:
            new_text = self.replace_words(raw_txt)
            self.txt_out_left.SetValue(new_text)
            return

        if self._selected_dir_path:
            fpath = self._selected_dir_path
            self.lb_path.LabelText = ''
            self._selected_dir_path = ''
            output_txt = ''

            for fn in os.listdir(fpath):
                filename = os.path.join(fpath, fn)
                with open(filename, 'r', encoding='utf8') as f:
                    txt = f.read()
                output_txt += self.replace_words(txt) + "\n\n"
            with open(os.path.join(fpath, 'merge.txt'), 'w', encoding='utf8') as f:
                f.write(output_txt)
        self.txt_out_right.SetValue("脱敏完成")

    def un_tuoming(self, event):
        '''
        反 脱敏（
        '''
        raw_txt = self.txt_in_left.GetValue()
        if not raw_txt:
            return

        replace_words = {}
        for v, k in self._replace_words:
            if k in replace_words:
                if len(v) > len(replace_words[k]):  # 保留最长的词
                    replace_words[k] = v
                continue
            replace_words[k] = v

        pattern = re.compile("|".join(map(re.escape, replace_words.keys())))  # 预编译
        txt = pattern.sub(lambda m: replace_words[m.group(0)], raw_txt)
        self.txt_out_left.SetValue(txt)

    def diff_excel(self, event):
        """
        对比两个excel的差异（对应的sheet的对应的行）
        :param event:
        :return:
        """
        if not self._selected_file_path.endswith('.xls') and not self._selected_file_path.endswith('.xlsx'):
            self.txt_out_right.SetValue("请选择源excel文件")
            return

        if not self._selected_file_path2.endswith('.xls') and not self._selected_file_path2.endswith('.xlsx'):
            self.txt_out_right.SetValue("请选择参照excel文件")
            return

        sheet_name_ls1, data_ls1 = self.read_xlsx(self._selected_file_path)
        sheet_name_ls2, data_ls2 = self.read_xlsx(self._selected_file_path2)

        results = []
        for name1, name2, data1, data2 in zip(sheet_name_ls1, sheet_name_ls2, data_ls1, data_ls2):
            if name1 != name2:
                self.txt_out_right.SetValue(f"【{name1}】和【{name2}】不匹配")
                return

            # 对data1和data2按行进行比较
            diffs = []
            row_cnt = min(len(data1), len(data2))
            for i in range(row_cnt):
                diff = self.is_diff(data1[i], data2[i])
                if diff:
                    diffs.append(diff)
            if diffs:
                head = '|'.join([' ' for v in range(len(data1[0]) + 2)])
                head += '\n|' + '|'.join(['---' for v in range(len(data1[0]))]) + '|\n'

                results.append(f'\n【{name1}】\n{head}{"".join(diffs)}\n')

        if not results:
            self.txt_out_right.SetValue("【无差异】")
            return

        results = '\n'.join(results)
        self.txt_out_right.SetValue(results)

        with open('diff.md', 'w', encoding='utf-8') as f:
            f.write(results)
        time.sleep(0.1)
        try:
            os.startfile('diff.md')
        except Exception as e:
            print(f"打开文件时出错：{e}")

    def extract_docx_titles(self, evt):
        self._extract_docx_titles(5)

    def extract_docx_titles4(self, evt):
        self._extract_docx_titles(4)

    def _extract_docx_titles(self, max_title_level):
        self.txt_out_right.SetValue('')
        self.txt_in_left.SetValue('')

        if not self._selected_file_path or not self._selected_file_path.endswith('.docx'):
            self.txt_out_right.SetValue("请选择docx文件")
            return
        doc = Document(self._selected_file_path)

        numbering = [0] * max_title_level  # 支持10级标题
        headings = []

        for para in doc.paragraphs:
            style_name = para.style.name
            print(style_name)
            if not style_name.startswith('标题') and not style_name.startswith('Heading'):
                continue

            level = to_int(style_name)
            if level > max_title_level:  # 太小的标题就不要了
                continue

            # 更新当前级别的编号并重置下级编号
            numbering[level - 1] += 1
            for i in range(level, len(numbering)):
                numbering[i] = 0

            # 构建编号前缀
            num_prefix = ".".join(str(n) for n in numbering[:level] if n > 0)
            full_heading = f"{num_prefix} {para.text.strip()}"

            full_heading = f'{"#" * level} {full_heading}'
            headings.append(full_heading)

        self.txt_in_left.SetValue('\n'.join(headings))


if __name__ == '__main__':
    app = wx.App()

    frame = FrmCompare(None)
    frame.Show()

    app.MainLoop()
